﻿/* 生成包含日期时间信息的GUID
 * 作者：xuzc
 * 最后更新时间：2015-2-13 9:15
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Xant.FlowEngine.Utility
{
    /// <summary>
    /// COMB (GUID与时间混合型) 类型 GUID 数据辅助类
    /// <remarks>
    /// COMB是一个特殊规则GUID类,其高(低)6位为日期信息,产生方法一般将原GUID实例的高(低)6位替换为当前时间信息
    /// </remarks>
    /// </summary>
    public static class CombKey
    {
        /// <summary>
        /// 返回COMB (GUID与时间混合型) 类型 GUID 数据，用于产生数据库主键值
        /// </summary>
        /// <param name="guid">Guid实例</param>
        /// <returns>返回COMB (GUID与时间混合型) 类型 GUID 数据</returns>
        public static Guid Combine(this Guid guid, bool replacedHighest = true)
        {
            return NewComb(guid, replacedHighest);
        }

        /// <summary>
        /// 返回 GUID 用于数据库操作，特定的时间代码可以提高检索效率
        /// </summary>
        /// <remarks>自动生成一个新GUID，将高6位将被替换为当前时间信息</remarks>
        /// <returns>COMB (GUID 与时间混合型) 类型 GUID 数据</returns>
        public static Guid NewComb(bool replacedHighest = true)
        {
            return NewComb(Guid.NewGuid(), replacedHighest);
        }


        //================================================================
        /// <summary>
        /// 返回 GUID 用于数据库操作，特定的时间代码可以提高检索效率
        /// </summary>
        /// <param name="guid"></param>
        /// <param name="replacedHighest">指示是否采用替换高6位为时间信息(默认为真)，为否时将低6位替换为时间信息。
        /// <remarks>对于像MySQL这样没有对GUID提供原生支持的数据库，在排序时并不会按照GUID值来排序的，而是按文本或是二进制数组来排序，此时是低位在前的</remarks>
        ///</param>
        /// <returns>COMB (GUID 与时间混合型) 类型 GUID 数据</returns>
        public static Guid NewComb(Guid guid, bool replacedHighest = true)
        {
            byte[] guidArray = guid.ToByteArray();
            DateTime baseDate = new DateTime(1900, 1, 1);
            DateTime now = DateTime.Now;
            //获得今天距离1900-1-1的天数，以Ticks数值表示
            TimeSpan days = new TimeSpan(now.Ticks - baseDate.Ticks);
            //获得当前时间除去日期部分后的Ticks值
            TimeSpan msecs = new TimeSpan(now.Ticks - (new DateTime(now.Year, now.Month, now.Day).Ticks));

            // Convert to a byte array 
            // Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333 
            byte[] daysArray = BitConverter.GetBytes(days.Days);
            byte[] msecsArray = BitConverter.GetBytes((long)(msecs.TotalMilliseconds / 3.333333));

            //上面生成的字节数组中，只有前半段有值，为方便加工，直接获取出需要的那半段
            Array.Resize(ref daysArray, 2);
            Array.Resize(ref msecsArray, 4);

            /*GUID中各字节存放的位置如下
             * new Guid(new byte[16] {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 }) => 03020100-0504-0706-0809-0a0b0c0d0e0f*/
            if (replacedHighest)//用时间信息替换高6位
            {
                // Reverse the bytes to match SQL Servers ordering 
                Array.Reverse(daysArray);
                Array.Reverse(msecsArray);
                Buffer.BlockCopy(daysArray, 0, guidArray, guidArray.Length - 6, 2);
                Buffer.BlockCopy(msecsArray, 0, guidArray, guidArray.Length - 4, 4);
            }
            else//用时间信息替换低6位
            {
                //Array.Copy(guidArray, 0, guidArray, 6, 10);
                //注意：这里要特别理解一下的是，GUID的组成(int+short+short+byte[6])，这里低4位的int值将必须拆成两半(日期放在高2字节，时间放在低2字节)，不能直接将时间信息放在int部分，否则排序将是乱的
                Buffer.BlockCopy(daysArray, 0, guidArray, 2, 2);//日期信息放在GUID的低3,4位（注意：以文本表示时，对应第1-4个字符）
                Buffer.BlockCopy(msecsArray, 2, guidArray, 0, 2);//时间信息分成两段，后2个字节放在GUID的低1,2位（注意：以文本表示时，对应第5-8个字符）
                Buffer.BlockCopy(msecsArray, 0, guidArray, 4, 2);//时间信息前两个字节放在GUID的低5,6位
            }

            return new System.Guid(guidArray);
        }

        //================================================================
        /// <summary>
        /// 获取COMB类型的GUID中所包含的日期时间信息
        /// </summary>
        /// <param name="guid">包含时间信息的 COMB </param>
        /// <param name="replacedHighest">指示该GUID是高6位被替换为日期时间信息，还是低6位被替换为日期时间信息。</param>
        /// <returns>时间</returns>
        public static DateTime GetDateFromComb(System.Guid guid, bool replacedHighest = true)
        {
            DateTime baseDate = new DateTime(1900, 1, 1);
            byte[] daysArray = new byte[4];
            byte[] msecsArray = new byte[4];
            byte[] guidArray = guid.ToByteArray();

            // Copy the date parts of the guid to the respective byte arrays. 
            if (replacedHighest)//格式为：用时间信息替换高6位
            {
                Buffer.BlockCopy(guidArray, guidArray.Length - 6, daysArray, 2, 2);
                Buffer.BlockCopy(guidArray, guidArray.Length - 4, msecsArray, 0, 4);
                // Reverse the arrays to put them into the appropriate order 
                Array.Reverse(daysArray);
                Array.Reverse(msecsArray);
            }
            else
            {
                Buffer.BlockCopy(guidArray, 2, daysArray, 0, 2);
                Buffer.BlockCopy(guidArray, 0, msecsArray, 2, 2);
                Buffer.BlockCopy(guidArray, 4, msecsArray, 0, 2);
            }


            // Convert the bytes to ints 
            int days = BitConverter.ToInt32(daysArray, 0);
            int msecs = BitConverter.ToInt32(msecsArray, 0);

            DateTime date = baseDate.AddDays(days);
            date = date.AddMilliseconds(msecs * 3.333333);

            return date;
        }

    }

}